# -*- coding: utf-8 -*-
import operator
from formencode.validators import FancyValidator, Invalid

__all__ = ["CPF"]

class CPF(FancyValidator):
    u"""
    Esta classe é responsável por validar um número de CPF, dentre outras coisas
    verificando se os dígitos de controle e os números do documento são
    compatíveis e verificar se a quantidade de dígitos informada é a correta.

    A utilização dá-se como para outros validadores do FormEncode, o que permite
    que este validador seja usado em conjunto com outros não providos aqui ou
    então em regras mais complexas de validação.


    Parâmetros:

    value    -> valor que sofrerá a ação
    messages -> dicionário contendo as mensagens de erro
    format_output -> informa se o valor retornado deve ou não ser formatado com
                     "." e "-" para a exibição
    strict   -> se verdadeiro, falha para números como 111.111.111-11
    allow_invalid_format -> informa se números como "1..11111.111-11" devem ou
                            não ser aceitos
    allow_missing_format -> informa se números como "11111111111" devem ou não
                            ser aceitos
    """

    format_output = True
    strict = True
    allow_invalid_format = True
    allow_missing_format = True
    allow_extra_digits = False
    messages = {
        'empty': 'Por favor entre com um CPF',
        'invalid': 'CPF invalido',
        'size': 'Numero de digitos incorreto',
        'format': 'Formatacao do numero incorreta',
        }

    def _to_python(self, value, state):
        def __checa_formato(value):
            if (value[3], value[7], value[11]) == ('.', '.', '-'):
                return value[:3] + value[4:7] + value[8:11] + value[12:]
            raise Invalid(self.message('format', state), value, state)

        if self.allow_invalid_format:
            value = value.replace('.', '').replace('-', '').replace('/', '')
            return value
        elif self.allow_missing_format:
            if len(value) == 11:
                return value
            if len(value) == 14:
                return __checa_formato(value)
        else: # not missing and not invalid
            if len(value) == 14:
                return __checa_formato(value)

        raise Invalid(self.message('format', state), value, state)


    def validate_python(self, value, state):
        if self.allow_extra_digits:
            value = value[-11:]

        if not len(value) == 11:
            raise Invalid(self.message('size', state), value, state)

        if not value.isdigit():
            raise Invalid(self.message('invalid', state), value, state)

        if self.strict:
            numbers = [11*str(digit) for digit in range(10)]
            if value in numbers:
                raise Invalid(self.message('invalid', state), value, state)

        resto = reduce(operator.add, [(10-i) * int(v)
                                      for i, v in enumerate(value[:9])]) % 11
        digito1 = (resto > 1) and (11 - resto) or 0
        resto = reduce(operator.add, [(11-i) * int(v)
                                      for i, v in enumerate(value[:10])]) % 11
        digito2 = (resto > 1) and (11 - resto) or 0

        if ([digito1, digito2] != [int(value[9]), int(value[10])]):
            raise Invalid(self.message('invalid', state), value, state)

    def _from_python(self, value, state):
        if len(value) == 11 and self.format_output:
            return "%s.%s.%s-%s" % (value[0:3], value[3:6],
                                    value[6:9], value[9:11])
        else:
            # Assume-se que o valor vem de uma fonte "correta" e pode chegar
            # ao usuário desta forma.
            return value
